golang 踩坑之

您所在的位置:网站首页 golang struct copy golang 踩坑之

golang 踩坑之

#golang 踩坑之| 来源: 网络整理| 查看: 265

golang中切片的拷贝

有这样一种场景,我需要用container.list来模拟queue,但是当我在循环list的过程中发现我追到container.list里的节点node的值没有生效,如下

type node struct { chars []rune count int }

定义的节点struct中有两个元素,一个chars是切片数组存放字符,另外一个是count作为一个简单的计数,代码如下:

func main() { n := node{[]rune{'A', 'A', 'A', 'A'}, 0} q := list.List{} for i := 0; i fmt.Println(e.Value) v := e.Value.(node) fmt.Printf("%s,address:%p\n", string(v.chars),&v.chars[0]) } }

结果输出如下:

C:\Users\zhww\AppData\Local\Temp\GoLand\___10go_build__go.exe {[77 80 73 75] 0} MPIK,address:0xc00001c0b0 {[77 80 73 75] 1} MPIK,address:0xc00001c0b0 {[77 80 73 75] 2} MPIK,address:0xc00001c0b0 {[77 80 73 75] 3} MPIK,address:0xc00001c0b0 {[77 80 73 75] 4} MPIK,address:0xc00001c0b0 {[77 80 73 75] 5} MPIK,address:0xc00001c0b0 {[77 80 73 75] 6} MPIK,address:0xc00001c0b0 {[77 80 73 75] 7} MPIK,address:0xc00001c0b0 {[77 80 73 75] 8} MPIK,address:0xc00001c0b0 {[77 80 73 75] 9} MPIK,address:0xc00001c0b0 进程 已完成,退出代码为 0

这个就不合理,所有我追加到list中的节点遍历得到的值都是一样的,就拿第一个输出来说,首次循环‘A’+rune(0),这个计算结果转换成string也就是’A’本身,没做任何变化,通过打印list的每个节点的chars中的第一个元素的地址,也就是chars[0]的取地址,发现所有的list节点,这个输出都是一样的,也就是我复制了10个节点,每个node的chars这个元素,它里面的值其实是公共的,只有一份,而不是10份; 而另外一个元素count每一个node,他的值都是不一样的,所以count是每一个node单独有一份。 image.png

切片是一个引用映射

也就是说,虽然golang在传递结构体给list的Pushback的时候是值拷贝,但是没有复制切片所映射指向的内存区域,所以,所有的复制的node,其chars切片元素的映射是在指向了同一块公共内存;而其结果当然是以最后一次变更的值为准。 所以这是golang切片的特性,那么我想要每个node有单独的内存区域,因为要实现queue的弹出操作,如果每次弹出的元素都一样,我无法在一些程序比如广度搜索的时候记录历史走过的坐标,因为不论你怎么记录,都只有一份,而且值还是你最后一次更新的坐标值。 一个很简便的办法就是在node结构体中chars不用切片,改用数组,这样在Pushback复制的时候,数组的复制就不存在这个问题,数组就是”最底层“的结构了,不存在映射或者指向的拷贝问题。 node把切片chars []rune 换成chars [4]rune

type node struct { chars [4]rune count int }

程序需要在node类型的变量n初始化的地方修改一下,另外最后的打印无法直接转换成string,需要经历一次数组的[:] 拷贝操作:

func main() { //这里实例化node的时候以数组方式实例化 n := node{[4]rune{'A', 'A', 'A', 'A'}, 0} q := list.List{} for i := 0; i fmt.Println(e.Value) v := e.Value.(node) // 这里在变为chars变为[4]rune数组后,无法直接string(v.chars)了,因为[4]rune这个类型没有string接口,所以直接使用[:] 拷贝chars [4]rune里的值然后转为string fmt.Printf("%s,address:%p\n", string(v.chars[:]), &v.chars[0]) } }

再次运行:

C:\Users\zhww\AppData\Local\Temp\GoLand\___10go_build__go.exe {[65 65 65 65] 0} AAAA,address:0xc00000c228 {[65 66 65 65] 1} ABAA,address:0xc00000c240 {[65 66 67 65] 2} ABCA,address:0xc00000c258 {[65 66 67 68] 3} ABCD,address:0xc00000c270 {[69 66 67 68] 4} EBCD,address:0xc00000c288 {[69 71 67 68] 5} EGCD,address:0xc00000c2a0 {[69 71 73 68] 6} EGID,address:0xc00000c2b8 {[69 71 73 75] 7} EGIK,address:0xc00000c2d0 {[77 71 73 75] 8} MGIK,address:0xc00000c2e8 {[77 80 73 75] 9} MPIK,address:0xc00000c300 进程 已完成,退出代码为 0

可以看到每次输出都不一样了,每个node现在都有独立的空间。 image.png

后来想了想,这其实及时一个老生常谈的深浅拷贝的问题,对于结构体在golang中传递默认是值拷贝,但是这是对于结构体本身来说,结构体就像是一个盒子,结构体中的切片或者指针就像是钥匙,你可以复制好几分盒子,复制好几把钥匙,但是钥匙对应的“房间”,在值传递的过程中没法每次单独复制一份对应到你复制的”盒子“,盒子的钥匙每复制一份就多一份,但是它们对应的房间始终是公共的一份; 要想避免这种问题,在结构体中就不要使用切片,而是改用数组。这样每次在拷贝复制的之后,每一份结构体都会有单独自己的空间。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3